package com.nervenets.web.controller.handler;


import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.nervenets.utils.*;
import com.nervenets.web.controller.Global;
import com.nervenets.web.controller.handler.common.TokenParams;
import com.nervenets.web.entity.MessageCode;
import com.nervenets.web.entity.Result;
import com.nervenets.web.enumeration.Role;
import com.nervenets.web.hibernate.dao.UserDao;
import com.nervenets.web.i18n.Translator;
import com.nervenets.web.rabbitmq.MessageProducer;
import com.nervenets.web.redis.GlobalService;
import com.nervenets.web.redis.GlobalVariablesService;
import com.nervenets.web.redis.utils.RedisSyncLocker;
import com.nervenets.web.validation.ValidateResult;
import org.weixin4j.Configuration;
import org.weixin4j.Weixin;
import org.weixin4j.WeixinException;
import org.weixin4j.http.OAuthToken;
import org.weixin4j.pay.*;
import org.weixin4j.pay.refund.Refund;
import org.weixin4j.pay.refund.RefundResult;
import org.weixin4j.pay.transfers.Transfers;
import org.weixin4j.pay.transfers.TransfersResult;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;

/**
 * 普通的接口Handler，无权限限制 Created by Joe on 17-3-26.
 */
public abstract class Handler<T extends ApiParams> {
    protected T params;
    protected MessageProducer messageProducer;
    protected UserDao userDao;
    protected GlobalService globalService;
    protected GlobalVariablesService globalVariablesService;
    protected RedisSyncLocker redisSyncLocker;
    protected PropertiesLoader configProperties;

    protected Handler() {
        Class clz = this.getClass();
        ParameterizedType type = (ParameterizedType) clz.getGenericSuperclass();
        Type[] types = type.getActualTypeArguments();
        Class<T> cls = (Class<T>) types[0];
        try {
            this.params = cls.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("T is invalid!");
        }
    }

    public String handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        params.init(request);
        ValidateResult valid = params.valid();
        if (valid.isValid()) {
            if (params instanceof TokenParams) {
                boolean result = false;
                try {
                    result = ((TokenParams) params).tokenIllegal();
                    if (!result) {
                        return errorMessage(MessageCode.code_777, "您的登录信息已失效，请重新登录");
                    }
                } catch (Exception ex) {
                    return errorMessage(MessageCode.code_778, "非法请求");
                }
                if (!(this instanceof SessionHandler)) {
                    long userId = ((TokenParams) params).getUserId();
                    if (globalVariablesService.canProceedCustom(String.format("ACTIVE_USER_%s", userId), JodaUtils.getEndOfToday() - JodaUtils.getTimestamp())) {//一天只算一次活跃就可以了
                        globalService.activeUser(userId);
                    }
                }
            }
            return handle(params, request);
        }

        return errorMessage(MessageCode.code_444, valid.getMessageString());
    }

    public abstract String handle(T params, HttpServletRequest request) throws Exception;

    public abstract String result() throws Exception;

    protected String successMessage() {
        return successMessage(null);
    }

    protected String successMessage(Object object) {
        Result result = Result.success();
        result.setCode(MessageCode.code_200);
        result.setData(object);
        result.setVersion("1.0");
        result.setMessage(Translator.translate("code_200"));
        return StringUtils.toJson(result);
    }

    @SafeVarargs
    protected final String successMessage(Object object, KeyValue... keyValues) {
        Result result = Result.success();
        result.setCode(MessageCode.code_200);
        result.setData(object);
        result.setVersion("1.0");
        result.setMessage(Translator.translate("code_200"));
        for (KeyValue v : keyValues) {
            result.put(v.key, v.value);
        }
        return StringUtils.toJson(result);
    }

    protected String errorMessage(int messageCode) {
        return errorMessage(messageCode, Translator.translate("code_" + messageCode));
    }

    protected String errorMessage(int messageCode, Object object) {
        Result result = Result.failed();
        result.setCode(messageCode);
        result.setData(object);
        result.setVersion("1.0");
        result.setMessage(Translator.translate("code_" + messageCode));
        return StringUtils.toJson(result);
    }

    protected String errorMessage(int messageCode, Object object, String customMessage) {
        Result result = Result.failed();
        result.setCode(messageCode);
        result.setData(object);
        result.setVersion("1.0");
        result.setMessage(customMessage);
        return StringUtils.toJson(result);
    }

    protected String errorMessage(int messageCode, String customMessage) {
        Result result = Result.failed();
        result.setCode(messageCode);
        result.setData(null);
        result.setVersion("1.0");
        result.setMessage(customMessage);
        return StringUtils.toJson(result);
    }

    public T getParams() {
        return params;
    }

    public boolean isAuthority() {
        return false;
    }

    public ArrayList<Role> getRoles() {
        return null;
    }

    public long getIp(HttpServletRequest request) {
        final String ipAddress = HttpTools.getIpAddress(request);
        return NumberUtil.ipToLong(ipAddress);
    }

    /**
     * 微信登录Token覆盖
     *
     * @throws WeixinException
     */
    protected void weixinLogin() throws WeixinException {
        OAuthToken weChatAuthToken = globalVariablesService.getWeChatAuthToken();
        Global.getWeixin().setOauthToken(weChatAuthToken);
    }

    /**
     * 微信转账 *
     *
     * @param tradeNo
     * @param openId
     * @param amount
     * @param description
     * @return
     * @throws WeixinException
     */
    protected TransfersResult transferMoney(String tradeNo, String openId, long amount, String description) throws WeixinException {
        Transfers transfers = new Transfers();
        transfers.setMch_appid(Configuration.getOAuthAppId());
        transfers.setMchid(Configuration.getPartnerId());
        transfers.setNonce_str(StringUtils.randomString(16));
        transfers.setPartner_trade_no(tradeNo);
        transfers.setOpenid(openId);
        transfers.setAmount(amount);
        transfers.setDesc(description);
        transfers.setSpbill_create_ip("120.77.56.17");
        transfers.setSign(SignUtil.getSign(transfers.toMap(), Configuration.getPartnerKey()));
        final TransfersResult transfersResult = Global.getWeixin().transferToUser(transfers, Configuration.getPartnerId(), Configuration.getCertPath(), Configuration.getCertSecret());
        System.out.println(StringUtils.toJson(transfers));
        return transfersResult;
    }

    /**
     * 微信退款 *
     *
     * @param tradeNo
     * @param amount
     * @return RefundResult
     * @throws WeixinException
     */
    protected RefundResult refund(String tradeNo, int amount) throws WeixinException {
        Refund refund = new Refund();
        refund.setAppid(Configuration.getOAuthAppId());
        refund.setMch_id(Configuration.getPartnerId());
        refund.setNonce_str(StringUtils.randomString(16));
        refund.setOut_trade_no(tradeNo);
        refund.setOut_refund_no(tradeNo);
        refund.setRefund_fee(amount);
        refund.setTotal_fee(amount);
        refund.setSign(SignUtil.getSign(refund.toMap(), Configuration.getPartnerKey()));
        final RefundResult refundResult = Global.getWeixin().refund(refund, Configuration.getPartnerId(), Configuration.getCertPath(), Configuration.getCertSecret());
        System.out.println(StringUtils.toJson(refund));
        return refundResult;
    }


    protected String aliPay(long orderId, String subject, long money, String orderNumber, HttpServletRequest request) {
        //实例化客户端
        AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", Global.Config.APP_ID, Global.Config.APP_PRIVATE_KEY, "json", "utf8", Global.Config.ALIPAY_PUBLIC_KEY, "RSA2");
//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称：alipay.trade.app.pay
        AlipayTradeAppPayRequest appPayRequest = new AlipayTradeAppPayRequest();
//SDK已经封装掉了公共参数，这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
        AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
        model.setSubject(subject);
        model.setOutTradeNo(orderNumber);
        model.setTimeoutExpress("30m");
        model.setTotalAmount(String.valueOf(NumberUtil.fen2Yuan(money)));
        model.setProductCode("QUICK_MSECURITY_PAY");
        appPayRequest.setBizModel(model);
        appPayRequest.setNotifyUrl(configProperties.getProperty("app.base.server") + "/api/v1/orderAliPaid");
        try {
            //这里和普通的接口调用不同，使用的是sdkExecute
            AlipayTradeAppPayResponse response = alipayClient.sdkExecute(appPayRequest);
            System.out.println(response.getBody());//就是orderString 可以直接给客户端请求，无需再做处理。
            return successMessage(response.getBody(), new KeyValue("orderId", orderId));
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        return null;
    }

    protected String wxPay(long orderId, String subject, long money, String orderNumber, HttpServletRequest request) throws WeixinException {
        final Weixin weixin = Global.getWeixin();
        UnifiedOrder unifiedOrder = new UnifiedOrder();
        unifiedOrder.setAppid(Configuration.getOAuthAppId());
        unifiedOrder.setMch_id(Configuration.getPartnerId());
        unifiedOrder.setNonce_str(StringUtils.randomString(16));
        unifiedOrder.setOut_trade_no(orderNumber);
        unifiedOrder.setBody(subject);
        unifiedOrder.setTotal_fee(money + "");
        unifiedOrder.setSpbill_create_ip(HttpTools.getIpAddress(request));
        unifiedOrder.setNotify_url(configProperties.getProperty("app.base.server") + "/api/v1/orderWxPaid");
        unifiedOrder.setTrade_type("APP");
        unifiedOrder.setSign(SignUtil.getSign(unifiedOrder.toMap(), Configuration.getPartnerKey()));
        System.out.println(unifiedOrder.toXML());

        final UnifiedOrderResult unifiedOrderResult = weixin.payUnifiedOrder(unifiedOrder);
        if (StringUtils.isEmpty(unifiedOrderResult.getPrepay_id()))
            return errorMessage(MessageCode.code_409, unifiedOrderResult);
        final WAPay waPay = PayUtil.getWAPay(Configuration.getOAuthAppId(), Configuration.getPartnerId(), unifiedOrderResult.getPrepay_id(), Configuration.getPartnerKey());
        return successMessage(JSONObject.toJSONString(waPay.toMap()), new KeyValue("orderId", orderId));
    }


    public void setMessageProducer(MessageProducer messageProducer) {
        this.messageProducer = messageProducer;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void setGlobalService(GlobalService globalService) {
        this.globalService = globalService;
    }

    public void setRedisSyncLocker(RedisSyncLocker redisSyncLocker) {
        this.redisSyncLocker = redisSyncLocker;
    }

    public void setGlobalVariablesService(GlobalVariablesService globalVariablesService) {
        this.globalVariablesService = globalVariablesService;
    }

    public void setConfigProperties(PropertiesLoader configProperties) {
        this.configProperties = configProperties;
    }

    public static class KeyValue {
        public String key;
        public Object value;

        public KeyValue(String key, Object value) {
            this.key = key;
            this.value = value;
        }
    }

    public static class ObjectKeyValue {
        public Object key;
        public Object value;

        public ObjectKeyValue(Object key, Object value) {
            this.key = key;
            this.value = value;
        }
    }
}
